Skip to content

Conversation

@VelikovPetar
Copy link
Contributor

@VelikovPetar VelikovPetar commented Oct 28, 2025

🎯 Goal

  • Adds the possibility to highlight mentions in the MessageComposer input field. For backwards compatibility, this is disabled by default.
  • Additionally allows inserting custom mentions (different from user mentions). Important: This is purely visual: The custom mentions are NOT submitted to the back-end, as the SDK doesn't support them. Their handling when sending the message needs to be fully implemented on integration side!

Usage

To customise the appearance of the mentions in the MessageComposer:

// Create custom `MentionStyleFactory`:
class CustomMentionStyleFactory : MentionStyleFactory {
    override fun styleFor(mention: Mention): SpanStyle? = when (mention) {
        is Mention.User -> SpanStyle(color = Color.Blue)
        else -> null
    }
}

// Pass it to the `ComposerInputFieldTheme` / `MessageComposerTheme`:
val messageComposerTheme = MessageComposerTheme
      .defaultTheme(isInDarkMode, typography, shapes, colors)
      .copy(
            inputField = ComposerInputFieldTheme.defaultTheme(
                mentionStyleFactory = CustomMentionStyleFactory(),
            )
       ),
)

// Pass it to `ChatTheme`:
ChatTheme(
    messageComposerTheme = messageComposerTheme,
) {
    MessagesScreen(/* ... */)
}

To select a custom mention:

// Define the mention type (ex. #channel mention)
val channelMentionType = MentionType("channel")

// Define the mention object:
object ChannelMention: Mention {
    override val type: MentionType = channelMentionType
    override val display: String = "channel"
}

// Select the mention via the `MessageComposerViewModel.selectMention(mention: Mention)` method.
// IMPORTANT: You must call this method, otherwise the composer cannot auto-detect the custom mentions.
messageComposerViewModel.selectMention(ChannelMention)
// would update the composer input by appending `channel` after the entered `@`

🛠 Implementation details

  • Introduce MentionType and Mention classes -> used to build custom mentions. Also defines the default
  • Introduce MessageComposerViewModel.selectMention(mention: Mention) to allow selecting custom mentions
  • Introduce MentionStyleFactory to allow customisation of the appearance in the MessageComposer
  • Separate buildAnnotatedMessageText into: buildAnnotatedMessageText and buildAnnotatedInputText - one for formatting message text (in bubble), and one for formatting input texts.

🎨 UI Changes

Sample only

Without styling With styling
without_highlight with_highlight

🧪 Testing

Default user mentions in sample:

  1. Open channel
  2. Type @ in composer
  3. Select a suggested user
  4. The mention should be highlighted in blue

Custom mentions:

  1. Apply the provided patch
  2. Open channel
  3. Type @ in composer
  4. Select the custom #channel mention option
  5. The mention should be added in the composer in cyan color
Provide the patch summary here
Subject: [PATCH] Remove redundant click listener.
---
Index: stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomChatComponentFactory.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomChatComponentFactory.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomChatComponentFactory.kt
--- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomChatComponentFactory.kt	(revision 7578cb71ee9e1b38bc8232ca47b92e1bf5f1ffc9)
+++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomChatComponentFactory.kt	(date 1761659311288)
@@ -37,12 +37,15 @@
 import io.getstream.chat.android.models.Poll
 import io.getstream.chat.android.models.User
 import io.getstream.chat.android.models.Vote
+import io.getstream.chat.android.ui.common.feature.messages.composer.mention.Mention
+import io.getstream.chat.android.ui.common.feature.messages.composer.mention.MentionType
 import io.getstream.chat.android.ui.common.state.messages.list.GiphyAction
 import io.getstream.chat.android.ui.common.state.messages.list.MessageItemState
 import io.getstream.chat.android.ui.common.state.messages.poll.PollSelectionType
 
 class CustomChatComponentFactory(
     private val delegate: ChatComponentFactory = MessageRemindersComponentFactory(),
+    private val onChannelMention: () -> Unit = {},
 ) : ChatComponentFactory by delegate {
 
     @Composable
@@ -118,4 +121,28 @@
             }
         }
     }
+
+    @Composable
+    override fun MessageComposerMentionsPopupContent(
+        mentionSuggestions: List<User>,
+        onMentionSelected: (User) -> Unit
+    ) {
+        // Dummy user to represent channel mention
+        val channelMention = User(id = "channel", name = "#channel")
+        val allSuggestions = listOf(channelMention) + mentionSuggestions
+        // Intercept the mention selection to handle channel mentions
+        val onMentionSelected: (User) -> Unit = { user ->
+            if (user.id == "channel") {
+                onChannelMention()
+            } else {
+                onMentionSelected(user)
+            }
+        }
+        super.MessageComposerMentionsPopupContent(allSuggestions, onMentionSelected)
+    }
+
+    private object ChannelMention: Mention {
+        override val type: MentionType = MentionType("channel")
+        override val display: String = "channel"
+    }
 }
Index: stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomMentionStyleFactory.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomMentionStyleFactory.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomMentionStyleFactory.kt
--- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomMentionStyleFactory.kt	(revision 7578cb71ee9e1b38bc8232ca47b92e1bf5f1ffc9)
+++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/component/CustomMentionStyleFactory.kt	(date 1761659414584)
@@ -20,6 +20,7 @@
 import androidx.compose.ui.text.SpanStyle
 import io.getstream.chat.android.compose.ui.theme.MentionStyleFactory
 import io.getstream.chat.android.ui.common.feature.messages.composer.mention.Mention
+import io.getstream.chat.android.ui.common.feature.messages.composer.mention.MentionType
 
 /**
  * A custom implementation of [MentionStyleFactory] that applies a specific color to user mentions.
@@ -30,6 +31,14 @@
 
     override fun styleFor(mention: Mention): SpanStyle? = when (mention) {
         is Mention.User -> SpanStyle(color = color)
+        is ChannelMention -> SpanStyle(color = Color.Cyan, fontWeight = androidx.compose.ui.text.font.FontWeight.Bold)
         else -> null
     }
 }
+
+object ChannelMention : Mention {
+    override val type: MentionType
+        get() = MentionType("channel")
+    override val display: String
+        get() = "channel"
+}
\ No newline at end of file
Index: stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
diff --git a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt
--- a/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt	(revision 7578cb71ee9e1b38bc8232ca47b92e1bf5f1ffc9)
+++ b/stream-chat-android-compose-sample/src/main/java/io/getstream/chat/android/compose/sample/ui/MessagesActivity.kt	(date 1761659311293)
@@ -63,6 +63,7 @@
 import io.getstream.chat.android.compose.sample.feature.channel.isGroupChannel
 import io.getstream.chat.android.compose.sample.ui.channel.DirectChannelInfoActivity
 import io.getstream.chat.android.compose.sample.ui.channel.GroupChannelInfoActivity
+import io.getstream.chat.android.compose.sample.ui.component.ChannelMention
 import io.getstream.chat.android.compose.sample.ui.component.CustomChatComponentFactory
 import io.getstream.chat.android.compose.sample.ui.component.CustomMentionStyleFactory
 import io.getstream.chat.android.compose.sample.ui.location.LocationPickerTabFactory
@@ -163,7 +164,9 @@
             shapes = shapes,
             typography = typography,
             attachmentsPickerTabFactories = attachmentsPickerTabFactories,
-            componentFactory = CustomChatComponentFactory(),
+            componentFactory = CustomChatComponentFactory(
+                onChannelMention = { composerViewModel.selectMention(ChannelMention) },
+            ),
             dateFormatter = ChatApp.dateFormatter,
             autoTranslationEnabled = ChatApp.autoTranslationEnabled,
             isComposerLinkPreviewEnabled = ChatApp.isComposerLinkPreviewEnabled,

@VelikovPetar VelikovPetar changed the title Highlight mentions in MessageComposer (Composer) Highlight mentions in MessageComposer (Compose) Oct 28, 2025
@github-actions
Copy link
Contributor

github-actions bot commented Oct 28, 2025

SDK Size Comparison 📏

SDK Before After Difference Status
stream-chat-android-client 3.23 MB 3.23 MB 0.00 MB 🟢
stream-chat-android-offline 3.45 MB 3.45 MB 0.00 MB 🟢
stream-chat-android-ui-components 10.54 MB 10.54 MB 0.00 MB 🟢
stream-chat-android-compose 12.76 MB 12.77 MB 0.01 MB 🟢

@VelikovPetar VelikovPetar requested a review from Copilot October 28, 2025 13:52
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds mention highlighting support to the MessageComposer input field in the Compose UI. The feature is disabled by default for backwards compatibility and includes support for custom mentions (visual only, not submitted to backend).

Key changes:

  • Introduces Mention and MentionType classes for building custom mentions
  • Adds MentionStyleFactory interface for customizing mention appearance
  • Separates text formatting logic into buildAnnotatedMessageText (for message bubbles) and buildAnnotatedInputText (for input fields)

Reviewed Changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
Mention.kt Defines MentionType and Mention interface with default Mention.User implementation
MessageComposerController.kt Updates mention selection logic to support both user and custom mentions
MessageComposerState.kt Adds selectedMentions field to track mentions in composer state
TextUtils.kt Splits annotation logic into separate functions for message bubbles and input fields
MessageComposerTheme.kt Adds MentionStyleFactory interface and integrates it into ComposerInputFieldTheme
MessageInput.kt Implements visual transformation to apply mention styling in the composer
InputField.kt Extracts default visual transformation logic and adds customization support
PollOptionInput.kt Updates to use buildAnnotatedInputText instead of buildAnnotatedMessageText
MessageComposerViewModel.kt Adds overload for selectMention accepting Mention parameter
CustomMentionStyleFactory.kt Sample implementation showing how to customize mention colors
MessagesActivity.kt Demonstrates integration of custom mention styling in the sample app
Test files Adds comprehensive tests for mention functionality
API files Documents public API changes for mention support

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +269 to +271
val start = text.indexOf(mention.display)
val end = start + mention.display.length
if (start < 0) return@forEach
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using indexOf will only find the first occurrence of the mention display text. If the same mention appears multiple times in the text, only the first occurrence will be styled. Consider finding all occurrences or using the mention's position information if available.

Copilot uses AI. Check for mistakes.
}

/**
*
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extra blank line at line 114 before the KDoc content. Remove the blank line after the opening '/**' to follow KDoc formatting conventions.

Suggested change
*

Copilot uses AI. Check for mistakes.
Comment on lines +275 to +276
addStyle(style, start - 1, end) // -1 to include the @ symbol
addStringAnnotation(AnnotationTagMention, mention.display, start - 1, end) // -1 to include the @ symbol
Copy link

Copilot AI Oct 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes the '@' symbol is always immediately before the mention display text. This assumption may not hold if the text is modified or if mentions are inserted in different contexts. Consider storing the actual position of mentions or validating that the character at start - 1 is indeed '@'.

Copilot uses AI. Check for mistakes.
@VelikovPetar VelikovPetar marked this pull request as ready for review October 28, 2025 13:59
@VelikovPetar VelikovPetar requested a review from a team as a code owner October 28, 2025 13:59
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
6.3% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants